const jwt = require('jsonwebtoken')
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'

// 配置 dayjs 插件
dayjs.extend(utc)
dayjs.extend(timezone)

interface DecodeToken {
  uid: number // 用户ID
  scope: string // 用户权限
  create_at: number // token创建时间
  expire_in: number // token有效期
  nickname: string // 用户昵称
}

export function findMembers(
  instance: any,
  {
    prefix,
    specifiedType,
    filter,
  }: {
    prefix?: any
    specifiedType?: any
    filter?: any
  },
) {
  // 递归函数
  function _find(instance: any): any {
    //基线条件（跳出递归）
    if (instance.__proto__ === null) return []

    let names = Reflect.ownKeys(instance)
    names = names.filter((name) => {
      // 过滤掉不满足条件的属性或方法名
      return _shouldKeep(name)
    })

    return [...names, ..._find(instance.__proto__)]
  }

  function _shouldKeep(value: any) {
    if (filter) {
      if (filter(value)) {
        return true
      }
    }
    if (prefix) if (value.startsWith(prefix)) return true
    if (specifiedType) if (instance[value] instanceof specifiedType) return true
  }

  return _find(instance)
}

/**
 * 生成token
 * @param uid 用户ID
 * @param scope 用户权限
 * @param nickname 用户昵称
 * @param expiresIn 过期时间(秒)
 */
export function generateToken(uid: number, scope: string, nickname: string, expiresIn?: number): string {
  const { secretKey } = global.config.security
  const token = jwt.sign(
    {
      uid,
      scope,
      nickname,
    },
    secretKey,
    {
      expiresIn: expiresIn || global.config.security.expiresIn
    },
  )
  return token
}

/**
 * token校验
 * @param token {String} 登录令牌
 * @returns {DecodeToken} token解码信息
 */
export function verifyToken(token: string): DecodeToken {
  const { secretKey } = global.config.security
  try {
    // 用户ID, 用户权限, 用户昵称, 创建时间, 有效期
    let {
      uid,
      scope,
      nickname,
      iat: create_at,
      exp: expire_in,
    } = jwt.verify(token, secretKey)
    return { uid, scope, nickname, create_at, expire_in }
  } catch (error: any) {
    let errMsg = 'TOKEN不合法, 请重新登录'
    // token 不合法
    if (error.name === 'JsonWebTokenError') errMsg = 'TOKEN不合法, 请重新登录'
    // token 过期
    if (error.name === 'TokenExpiredError') errMsg = '登录状态已过期, 请重新登录'
    // token 为空
    if (token === '') errMsg = '您当前未登录, 请先登录'
    // 统一抛出
    throw new global.errs.AuthFailed(errMsg)
  }
}

/**
 * 分页参数接口
 */
export interface PaginationParams {
  page: number;
  pageSize: number;
}

/**
 * 分页结果接口
 */
export interface PaginationResult<T> {
  total: number;
  list: T[];
  page: number;
  pageSize: number;
  totalPages: number;
}

/**
 * 分页工具类
 */
export class Pagination {
  /**
   * 计算分页偏移量
   * @param page 当前页码
   * @param pageSize 每页数量
   */
  static getOffset(page: number, pageSize: number): number {
    return (page - 1) * pageSize;
  }

  /**
   * 计算总页数
   * @param total 总记录数
   * @param pageSize 每页数量
   */
  static getTotalPages(total: number, pageSize: number): number {
    return Math.ceil(total / pageSize);
  }

  /**
   * 创建分页结果
   * @param total 总记录数
   * @param list 当前页数据
   * @param params 分页参数
   */
  static createResult<T>(
    total: number,
    list: T[],
    params: PaginationParams
  ): PaginationResult<T> {
    const { page, pageSize } = params;
    return {
      total,
      list,
      page,
      pageSize,
      totalPages: this.getTotalPages(total, pageSize)
    };
  }
}

/**
 * 时间格式化工具类
 */
export class DateUtil {
  /**
   * 格式化日期为 'YYYY-MM-DD HH:mm:ss' 格式
   * @param date 日期对象或时间戳
   * @returns 格式化后的日期字符串
   */
  static formatDateTime(date: Date | string | number): string {
    return dayjs(date).tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
  }

  /**
   * 格式化日期为 'YYYY-MM-DD' 格式
   * @param date 日期对象或时间戳
   * @returns 格式化后的日期字符串
   */
  static formatDate(date: Date | string | number): string {
    return dayjs(date).tz('Asia/Shanghai').format('YYYY-MM-DD');
  }

  /**
   * 获取当前时间戳（毫秒）
   * @returns 时间戳
   */
  static getCurrentTimestamp(): number {
    return Date.now();
  }

  /**
   * 获取当前时间（格式：YYYY-MM-DD HH:mm:ss）
   * @returns 当前时间字符串
   */
  static getCurrentDateTime(): string {
    return this.formatDateTime(new Date());
  }

  /**
   * 将日期字符串转换为时间戳
   * @param dateStr 日期字符串
   * @returns 时间戳
   */
  static toTimestamp(dateStr: string): number {
    return dayjs(dateStr).tz('Asia/Shanghai').valueOf();
  }

  /**
   * 计算月数 或 天数
   * @param startDate 开始日期
   * @param endDate 结束日期
   * @param key 值类型 (month || day)
   * @returns 天|月数
   */
  static diffMonthsOrDays(startDate: string | Date, endDate: string | Date, key: 'month' | 'day'): number {
    return dayjs(endDate).tz('Asia/Shanghai').diff(dayjs(startDate).tz('Asia/Shanghai'), key);
  }

  /**
   * 计算天数差
   * @param startDate 开始日期
   * @param endDate 结束日期
   * @returns 天数差
   */
  static diffDays(startDate: string | Date, endDate: string | Date): number {
    return dayjs(endDate).tz('Asia/Shanghai').diff(dayjs(startDate).tz('Asia/Shanghai'), 'day');
  }
  
  /**
   * 计算月或者天数
   * @param date 日期
   * @param number 值 (1个月 || n天)
   * @param key 值类型 (month || day)
   * @returns 计算月或者天数后的日期
   */
  static addMonthsOrDays(date: string | Date, number: number, key: 'month' | 'day'): string | Date {
    return dayjs(date).tz('Asia/Shanghai').add(number, key).toDate();
  }
}
